Repository navigation
Retry transient Git config reads - #2130
Merged
Tyrie Vella (tyrielv) merged 7 commits intoNov 4, 2026
Merged
Conversation
Tyrie Vella (tyrielv)
force-pushed
the
tyrielv/config-read-retry
branch
from
October 21, 2026 21:14
0a6acb3 to
69cbecc
Compare
Tyrie Vella (tyrielv)
force-pushed
the
tyrielv/config-read-retry
branch
2 times, most recently
from
November 2, 2026 18:58
3b374c2 to
7ac1f84
Compare
Tyrie Vella (tyrielv)
marked this pull request as ready for review
November 3, 2026 17:03
Tyrie Vella (tyrielv)
force-pushed
the
tyrielv/config-read-retry
branch
from
November 3, 2026 17:03
7ac1f84 to
c1bf978
Compare
|
[KeithBot]: Validation
🤖 KeithBot · claude-opus-5.5, gpt-6.1-sol, gemini-4-argon · reply on a thread to dispute |
Use one bounded retry policy for git.exe and libgit2 config reads. Retry only transient file access failures and preserve existing missing-key behavior. Retry the libgit2 repository open, which parses .git\config. Retry the git config alias lookup in the hooks. Let latency-sensitive hook callers use a smaller attempt budget. Match a transient failure only on the reason after the last "': " in an error line. A path or key that contains "Permission denied" no longer matches. Trace when a transient failure persists after all attempts. Do not trace config names that contain URL credentials. Add Enlistment.CreateGitProcess(ITracer). Use it for the mount cache server, retry, status cache, and required-config readers, so retries reach the mount trace. Assisted-by: GPT-5.6 Sol Assisted-by: Claude Sonnet 5.5 Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
RetryConfig started git.exe once for each of gvfs.max-retries, gvfs.timeout-seconds, and gvfs.read-timeout-seconds. Read the settings in process with libgit2. Open a transient repository without its object store when the working directory exists. Otherwise, read the global, XDG, system, and ProgramData config, as GitProcess.GetFromConfig does before clone creates the repository. The reads use the GitConfigReadRetry policy. libgit2 does not read GIT_CONFIG_PARAMETERS, GIT_CONFIG_COUNT, GIT_CONFIG_GLOBAL, GIT_CONFIG_SYSTEM, GIT_CONFIG_NOSYSTEM, or GIT_CONFIG. When the environment sets any of these variables, read the settings with git.exe. libgit2 also ignores GIT_DIR. Do not read with git.exe for it. Git exports GIT_DIR to hooks in linked worktrees, and GVFS reads repositories through an explicit path. Before clone creates the repository, an inherited GIT_DIR names an unrelated repository, so read only the global and system config. libgit2 evaluates only the gitdir, gitdir/i, and onbranch includeIf conditions. It skips other conditions, such as hasconfig:remote.*.url, without an error. When the config has such a condition, the libgit2 read fails, and git.exe reads the setting. When libgit2 cannot read a setting for another reason, for example because it rejects the repository's ownership, read the setting with git.exe and trace RetryConfig_ReadWithGit. After a transient file access failure, do not read again with git.exe, because libgit2 already used the retry budget. Move integer setting parsing to GitConfigHelper, so both readers report the same default, minimum, and parse errors. As before, an invalid or unreadable setting uses its default and RetryConfig traces a warning. When the LibGit2Repo constructor fails, it releases its libgit2 reference. Suppress finalization in that case, so the finalizer does not release the reference a second time. Assisted-by: Claude Opus 5.5 Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
Credential fill had its own retry loop for a transient config read failure. It now uses GitConfigReadRetry, so one policy sets the attempt count, the delays, and the traces for every config read. Credential fill keeps its stricter check: it retries only an empty-output failure in which git reports a transient file access warning and its config read failure, and no other error. Each attempt still gets only the time that remains of the caller's timeout. Add two optional parameters to GitConfigReadRetry.InvokeResult. canWait stops the retries when the caller's time budget cannot cover the next delay. beforeRetry receives each failed result that will be read again. Credential fill uses beforeRetry to write git's error output to the local log. Credential fill now reports retries with the GitConfigReadRetry warnings (ConfigName "credential fill"), not with the CredentialConfigReadFailure event. The transient reasons come from the shared list. Update the credential fill unit tests to assert the shared traces. Add a unit test for canWait and beforeRetry. Assisted-by: Claude Opus 5.5 Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
The RetryConfig reader trusted three include layouts that libgit2
evaluates differently from git.exe:
- onbranch: on a detached HEAD. libgit2 can apply the include, because
it does not set the match result in that case. git.exe does not.
- A gitdir: or gitdir/i: pattern that starts with a slash. git.exe
treats it as a path without a drive. libgit2 matches it anywhere, as
if it started with "**".
- An include.path or includeIf.*.path that uses %(prefix). git.exe
expands it to its installation directory. libgit2 skips the include.
The libgit2 read now fails for these layouts, and git.exe reads the
setting. libgit2 is still used for gitdir: and gitdir/i: patterns that
do not start with a slash.
Add unit tests that compare the reader with the installed git.exe for
onbranch: on a branch and on a detached HEAD, and for drive-less gitdir:
patterns. Add unit tests that check that %(prefix) include paths are read
with git.exe.
Assisted-by: Claude Opus 5.5
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
Tyrie Vella (tyrielv)
force-pushed
the
tyrielv/config-read-retry
branch
from
November 3, 2026 20:04
350bc38 to
573684b
Compare
…test AuthenticationInitializationLoadsAllRetrySettings counted the git config commands that the mock git process ran. RetryConfig now reads the settings with libgit2 unless the environment changes Git config, so the test failed when no GIT_CONFIG_* variable was set. The test now gives the same values to libgit2, through an isolated global config, and to the mock git process. It checks the values in the RetryConfig_LoadedRetryConfig event, so it passes on either read path. Assisted-by: Claude Opus 5.5 Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
LibGit2Repo.Dispose freed the repository handle and called git_libgit2_shutdown for every instance. The protected constructors that test doubles use do not call git_libgit2_init, so disposing or finalizing such an instance released a libgit2 reference that another owner held. When the count reached zero, libgit2 released its global state, including the config search paths that IsolatedLibGit2Config sets. Tests that ran later read the machine's global config instead. Because a finalizer could run at any time, the authentication retry-settings test failed only in some CI runs. Record whether the constructor initialized libgit2, and release the repository handle and the libgit2 reference only in that case. Add a unit test that disposes a MockLibGit2Repo while an isolated config is active and checks that a later read still uses the isolated config. Assisted-by: Claude Opus 5.5 Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
|
[KeithBot]: ✅ Resolved · iteration 9 (
Validation
Approving. 🤖 KeithBot · recheck by claude-opus-5.5 · reply on a thread to dispute |
Keith Klein (KeithIsSleeping)
approved these changes
Nov 3, 2026
git for Windows reads the global config from one home directory: HOME, else HOMEDRIVE and HOMEPATH, else USERPROFILE. libgit2 searches all of these directories and uses the first one that contains .gitconfig. For the XDG file, libgit2 also searches APPDATA and LOCALAPPDATA, which git does not. So when HOME points to a directory without a .gitconfig, libgit2 can read the profile .gitconfig while git reads no global file. The retry settings then differ between the two readers. Before the libgit2 reader returns a value, compare the global and XDG files that libgit2 selects with the files that git for Windows reads. If they differ, fail the libgit2 read, so that RetryConfig reads the value with git.exe. Do not change the libgit2 search paths, because that state is process-wide. Make IsolatedLibGit2Config set HOME and XDG_CONFIG_HOME to match its search paths. Add a unit test that sets HOME to an empty directory and USERPROFILE to a directory with a .gitconfig. The test checks that the libgit2 reader fails, that real git.exe reads no value, and that RetryConfig uses the default value. Assisted-by: Claude Opus 5.5 Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
Nathan Baird (ShiningMassXAcc)
approved these changes
Nov 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem and Context
Concurrent Git processes can replace
.git\configwhile VFS for Git reads it. On Windows, a reader can then get a shortPermission deniedor sharing error. This happens, for example, when several worktree mounts read the shared config while one mount writes it.VFS for Git reads configuration through
git.exeand through libgit2. A retry in only one of them would leave the other config readers exposed to the same race.RetryConfigalso startedgit.exethree times on every load, once each forgvfs.max-retries,gvfs.timeout-seconds, andgvfs.read-timeout-seconds. Mount, clone, prefetch, and dehydrate load it.Changes
Transient config read retry
GitConfigReadRetry, forgit.exeand libgit2 config reads. It makes five attempts, with exponential delays from 20 to 160 milliseconds..git\config.git credential fill. Credential fill keeps its stricter check: it retries only an empty-output failure in which git reports a transient file access warning and its config read failure. Each attempt gets only the time that remains of the caller's timeout. Credential fill now reports retries with the shared retry traces, not with a separateCredentialConfigReadFailureevent.Enlistment.CreateGitProcess(ITracer), so that the mount's config readers report retries to the mount trace.RetryConfig reads with libgit2
gvfs.max-retries,gvfs.timeout-seconds, andgvfs.read-timeout-secondsin process with libgit2. An invalid or unreadable setting still uses its default with a warning. Open a transient repository without its object store. Before clone creates the repository, read the global, XDG, system, and ProgramData config, as thegit.exepath does.git.exewhen libgit2 cannot give the value thatgit.exegives:GIT_CONFIG_PARAMETERS,GIT_CONFIG_COUNT,GIT_CONFIG_GLOBAL,GIT_CONFIG_SYSTEM,GIT_CONFIG_NOSYSTEM, orGIT_CONFIG. libgit2 also ignoresGIT_DIR, but the reader does not fall back for it: Git exportsGIT_DIRto hooks in linked worktrees, GVFS reads repositories through an explicit path, and before clone creates the repository an inheritedGIT_DIRnames an unrelated repository;gitdir:orgitdir/i:(libgit2 skipshasconfig:remote.*.urlwithout an error, and can applyonbranch:on a detached HEAD), agitdir:pattern that starts with a slash, or a path that uses%(prefix);.gitconfigfrom one home directory (HOME, else HOMEDRIVE and HOMEPATH, else USERPROFILE). libgit2 uses the first of these directories that contains the file. It also searches APPDATA and LOCALAPPDATA for the XDG file;RetryConfig_ReadWithGitin this case.git.exe, because libgit2 already used the retry budget.GitConfigHelper, so that both readers report the same default, minimum, and parse errors.LibGit2Repoconstructor fails, suppress finalization. The constructor already released its libgit2 reference, and the finalizer released it a second time.Tests
git.exe. The tests check that ahasconfig:include, anonbranch:include on a branch and on a detached HEAD, and agitdir:pattern without a drive are read throughgit.exewith the value thatgit.exereads. Other tests check that%(prefix)include paths are read throughgit.exe..gitconfig. The test checks that the libgit2 reader fails, that the installedgit.exereads no value, and that RetryConfig uses the default.git.exe, and for the fallback.Known gaps and deliberate decisions
git.exeroutes the read togit.exe. Agvfs.*flag would itself need a config read.git.exe.--fileor--globalread can look unset —git config --fileexits 1 with onlywarning:lines, whichConfigResulttreats as "not configured".GetConfigStringcannot observe a locked config — libgit2 serves its parsed snapshot, so its retry helpsGetConfigBooland repository open.HttpRequestor, and the static global and system readers do not report retries.